// -*- C++ -*-
//===-- discard_block_engine.h ---------------------------------------------===//
//
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// This file incorporates work covered by the following copyright and permission
// notice:
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
//
//===----------------------------------------------------------------------===//
//
// Abstract:
//
// Public header file provides implementation for Discard Block Engine

#ifndef DPSTD_DISCARD_BLOCK_ENGINE
#define DPSTD_DISCARD_BLOCK_ENGINE

namespace oneapi
{
namespace dpl
{

template <class _Engine, size_t _P, size_t _R>
class discard_block_engine
{
  public:
    // Engine types
    using result_type = typename _Engine::result_type;
    using scalar_type = internal::element_type_t<result_type>;

    // Engine characteristics
    static constexpr size_t block_size = _P;
    static constexpr size_t used_block = _R;
    static constexpr scalar_type
    min()
    {
        return _Engine::min();
    }
    static constexpr scalar_type
    max()
    {
        return _Engine::max();
    }

    // Constructors
    discard_block_engine() : n_(0) {}

    explicit discard_block_engine(const _Engine& __e) : engine_(__e), n_(0) {}
    explicit discard_block_engine(_Engine&& __e) : engine_(::std::move(__e)), n_(0) {}
    explicit discard_block_engine(scalar_type __seed, unsigned long long __offset = 0)
    {
        engine_.seed(__seed);

        // Discard offset
        discard(__offset);
    }

    // Seeding function
    void
    seed()
    {
        n_ = 0;
        engine_.seed();
    }

    void
    seed(scalar_type __seed)
    {
        n_ = 0;
        engine_.seed(__seed);
    }

    // Discard procedure
    void
    discard(unsigned long long __num_to_skip)
    {
        if (!__num_to_skip)
            return;

        for (; __num_to_skip > 0; --__num_to_skip)
            generate_internal_scalar<internal::type_traits_t<result_type>::num_elems>();
    }

    // operator () returns bits of engine recurrence
    result_type
    operator()()
    {
        result_type res = generate_internal<internal::type_traits_t<result_type>::num_elems>();

        return res;
    }

    // operator () overload for result portion generation
    result_type
    operator()(unsigned int __randoms_num)
    {
        result_type res = generate_internal<internal::type_traits_t<result_type>::num_elems>(__randoms_num);
        return res;
    }

    // Property function
    const _Engine&
    base() const noexcept
    {
        return engine_;
    }

  private:
    // Static asserts
    static_assert((0 < _R) && ( _R <= _P),
        "oneapi::dpl::discard_block_engine. Error: unsupported parameters");

    // Function for state adjustment
    template <int _N>
    typename ::std::enable_if<(_N == 0), scalar_type>::type
    generate_internal_scalar()
    {
        if (n_ >= static_cast<int>(used_block))
        {
            engine_.discard(static_cast<unsigned long long>(block_size - used_block));
            n_ = 0;
        }
        ++n_;
        return engine_();
    };

    template <int N>
    typename ::std::enable_if<(N > 0), scalar_type>::type
    generate_internal_scalar()
    {
        if (n_ >= static_cast<int>(used_block))
        {
            engine_.discard(static_cast<unsigned long long>(block_size - used_block));
            n_ = 0;
        }
        ++n_;
        return static_cast<scalar_type>(engine_(1u)[0]);
    };

    // Generate implementation
    template <int _N>
    typename ::std::enable_if<(_N == 0), result_type>::type
    generate_internal()
    {
        return generate_internal_scalar<internal::type_traits_t<result_type>::num_elems>();
    }

    template <int _N>
    typename ::std::enable_if<(_N > 0), result_type>::type
    generate_internal()
    {
        result_type __res;
        for (int __i = 0; __i < _N; ++__i)
        {
            __res[__i] = generate_internal_scalar<internal::type_traits_t<result_type>::num_elems>();
        }

        return __res;
    }

    template <int _N>
    typename ::std::enable_if<(_N > 0), result_type>::type
    generate_internal(unsigned int __randoms_num)
    {
        result_type __res;
        for (unsigned int __i = 0; __i < __randoms_num; ++__i)
        {
            __res[__i] = generate_internal_scalar<internal::type_traits_t<result_type>::num_elems>();
        }

        return __res;
    }

    _Engine engine_;
    int n_ = 0u;
};

} // namespace dpl
} // namespace oneapi

#endif // ifndef DPSTD_DISCARD_BLOCK_ENGINE